//===========================================================================
//                     __________       __
//                    * Copyright* 2006*  * 2007
//                    +=====+    *_____*  *
//                         /  * Hanli Zhao@ Swalloware
//                       /  / *   =====   *
//                     /   /  *  *     *  *
//                   +    +===+  *     *  +=====+
//                   *___________*     *________*
//
//                         "Spring swallows,
//          that resided in the front of courtyard of lords,
//               enter the house of ordinary citizens."
//          by Yuxi Liu, famous poet of Tang Dynasty of China
//
//                   mailto:Swalloware@gmail.com
//
//===========================================================================
//#include "Swalloware.h"
#include "dxstdafx.h"
#include "SCamera.h"

//===========================================================================
// used by SCamera to map WM_KEYDOWN keys
//===========================================================================
enum
{
    CAM_MOVE_LEFT_S     = 0x01,
    CAM_MOVE_RIGHT_S    = 0x02,
    CAM_MOVE_FORWARD_S  = 0x04,
    CAM_MOVE_BACKWARD_S = 0x08,
    CAM_MOVE_UP_S       = 0x10,
    CAM_MOVE_DOWN_S     = 0x20,
    CAM_MOUSE_S         = 0x40,
    CAM_RESET_S         = 0x80
};

SCamera::SCamera(void)
{
	mvMousePos            = D3DXVECTOR2(0.f, 0.f);
	mvMouseLockedPos      = D3DXVECTOR2(0.f, 0.f);
	mvRotAngle           = D3DXVECTOR2(0.f, 0.f);
    // Setup the projection matrix
    SetProjParams( 3.141592654f / 4.f, 1.f, 0.01f, 10.f );
    // Setup the view matrix
    SetViewParams( &D3DXVECTOR3(0.f, 0.f, 10.f), &D3DXVECTOR3(0.f, 0.f, 0.f) );
	SetScalers();

	mKeyMask = 0x00;
}

SCamera::~SCamera(void)
{
}
 
//===========================================================================
// Client can call this to change the position and direction of camera
//===========================================================================
void SCamera::SetViewParams( D3DXVECTOR3* pvEyePos, D3DXVECTOR3* pvLookAtPos )
{
    mvDefaultEye    = mvEye    = *pvEyePos;
    mvDefaultLookAt = mvLookAt = *pvLookAtPos;
    // Calc the view matrix
    D3DXMatrixLookAtLH( &mmxView, &mvEye, &mvLookAt, &D3DXVECTOR3(0, 1, 0) );
    D3DXMatrixInverse( &mmxInvView, NULL, &mmxView );
    // The axis basis vectors and camera position are stored inside the 
    // position matrix in the 4 rows of the camera's world matrix.
    // To figure out the yaw/pitch of the camera, we just need the Z basis vector
    mvRotAngle.x = atan2f( mmxInvView._31, mmxInvView._33 );
    float fLen = sqrtf(mmxInvView._33*mmxInvView._33 + mmxInvView._31*mmxInvView._31);
    mvRotAngle.y = -atan2f( mmxInvView._32, fLen );
	//mFrustum.OnFrameMove(&(mmxView*mmxProj));
}

//===========================================================================
// Calculates the projection matrix based on input params
//===========================================================================
void SCamera::SetProjParams( float fFOV, float fAspect, float fNearPlane, float fFarPlane )
{
    // Set attributes for the projection matrix
    D3DXMatrixPerspectiveFovLH( &mmxProj, fFOV, fAspect, fNearPlane, fFarPlane );
	mvNearFarPlane.x = fNearPlane;
	mvNearFarPlane.y = fFarPlane;
	//mFrustum.OnFrameMove(&(mmxView*mmxProj));
}

void SCamera::LockMouse()
{
	mKeyMask &= ~CAM_MOUSE_S;
}

bool SCamera::OnFrameMove(float fElapsedTime)
{
	if(! mKeyMask) return true;
	// Reset view parameters
	if(mKeyMask & CAM_RESET_S)
	{
		SetViewParams(&mvDefaultEye, &mvDefaultLookAt);
	}
	else
	{
		ProcessEvent(fElapsedTime);
		D3DXMatrixInverse(&mmxInvView, NULL, &mmxView);
		//mFrustum.OnFrameMove(&(mmxView*mmxProj));
	}
	return true;
}

bool SCamera::ProcessEvent(float fElapsedTime)
{
	D3DXVECTOR3 vWorldUp(0, 1, 0), vWorldAhead(0, 0, 1), vSpeed(0, 0, 0);
	// Perform rotation according to right mouse drag
	if((mKeyMask & CAM_MOUSE_S) && (mvMouseLockedPos != mvMousePos))
	{
		mvRotAngle += (mRotationScaler * (mvMousePos - mvMouseLockedPos));
		mvMouseLockedPos = mvMousePos;
		if(mvRotAngle.y > 1.5708f * 4) mvRotAngle.y = 0.f;
		else if(mvRotAngle.y < -1.5708f * 4) mvRotAngle.y = 0.f;
	}
	// Make a rotation matrix based on the camera's yaw & pitch
	D3DXMATRIX mxCameraRot;
	D3DXMatrixRotationYawPitchRoll( &mxCameraRot, mvRotAngle.x, mvRotAngle.y, 0 );
	D3DXVec3TransformCoord( &vWorldUp, &vWorldUp, &mxCameraRot );
	D3DXVec3TransformCoord( &vWorldAhead, &vWorldAhead, &mxCameraRot );
	// Perform translation according to keyboard & scroll input
	if(mKeyMask & CAM_MOVE_LEFT_S)     vSpeed.x -= 1;
	if(mKeyMask & CAM_MOVE_RIGHT_S)    vSpeed.x += 1;
	if(mKeyMask & CAM_MOVE_FORWARD_S)  vSpeed.z += 1;
	if(mKeyMask & CAM_MOVE_BACKWARD_S) vSpeed.z -= 1;
	if(mKeyMask & CAM_MOVE_UP_S)       vSpeed.y += 1;
	if(mKeyMask & CAM_MOVE_DOWN_S)     vSpeed.y -= 1;
	if(vSpeed != (0,0,0))
	{
		// Scale the acceleration vector
		D3DXVec3Normalize( &vSpeed, &vSpeed );
		vSpeed *= fElapsedTime * mfMoveScaler;
		D3DXVec3TransformCoord(&vSpeed, &vSpeed, &mxCameraRot );
		// Move the eye position 
		mvEye += vSpeed;
	};
    // Update the lookAt position based on the eye position 
    mvLookAt = mvEye + vWorldAhead;
    // Update the view matrix
    D3DXMatrixLookAtLH( &mmxView, &mvEye, &mvLookAt, &vWorldUp );
	return true;
}

void SCamera::OnMouseEvent(int x, int y, bool bMouseLocked)
{
	mvMousePos.x = (float)x;
	mvMousePos.y = (float)y;
	if(!(mKeyMask & CAM_MOUSE_S)) mvMouseLockedPos = mvMousePos;
	if(bMouseLocked) mKeyMask |= CAM_MOUSE_S;
	else mKeyMask &= ~CAM_MOUSE_S;
}

void SCamera::OnKeyEvent(int key, bool bKeyLocked)
{
	if(bKeyLocked)
	{
		switch(key)
		{
		case VK_LEFT:  mKeyMask |= CAM_MOVE_LEFT_S;     break;
		case VK_RIGHT: mKeyMask |= CAM_MOVE_RIGHT_S;    break;
		case VK_UP:    mKeyMask |= CAM_MOVE_FORWARD_S;  break;
		case VK_DOWN:  mKeyMask |= CAM_MOVE_BACKWARD_S; break;
		case VK_PRIOR: mKeyMask |= CAM_MOVE_UP_S;       break;
		case VK_NEXT:  mKeyMask |= CAM_MOVE_DOWN_S;     break;
		case VK_HOME:  mKeyMask |= CAM_RESET_S;         break;
		}
	}
	else
	{
		switch(key)
		{
		case VK_LEFT:  mKeyMask &= ~CAM_MOVE_LEFT_S;     break;
		case VK_RIGHT: mKeyMask &= ~CAM_MOVE_RIGHT_S;    break;
		case VK_UP:    mKeyMask &= ~CAM_MOVE_FORWARD_S;  break;
		case VK_DOWN:  mKeyMask &= ~CAM_MOVE_BACKWARD_S; break;
		case VK_PRIOR: mKeyMask &= ~CAM_MOVE_UP_S;       break;
		case VK_NEXT:  mKeyMask &= ~CAM_MOVE_DOWN_S;     break;
		case VK_HOME:  mKeyMask &= ~CAM_RESET_S;         break;
		}
	}
}
LRESULT SCamera::HandleMessages( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	if(uMsg == WM_LBUTTONDOWN)
	{
		bLeftButtonDown = true;
	}

	if(uMsg==WM_MOUSEMOVE)
	{

		int iMouseX = (short)LOWORD(lParam);
		int iMouseY = (short)HIWORD(lParam);
		OnMouseEvent(iMouseX,iMouseY,bLeftButtonDown);
	}

	if( uMsg == WM_LBUTTONUP )
	{
		LockMouse();
		bLeftButtonDown = false;
	}

	return FALSE;
}